Skip to content

fix(mcp): self-exit on stdin EOF/broken pipe so orphaned stdio sessions release locks - #2072

Open
xcarbo wants to merge 1 commit into
MemPalace:developfrom
xcarbo:contrib/stdin-eof-self-exit
Open

fix(mcp): self-exit on stdin EOF/broken pipe so orphaned stdio sessions release locks#2072
xcarbo wants to merge 1 commit into
MemPalace:developfrom
xcarbo:contrib/stdin-eof-self-exit

Conversation

@xcarbo

@xcarbo xcarbo commented Jul 24, 2026

Copy link
Copy Markdown

Problem

A stdio mcp_server orphaned by a dropped SSH session (ssh <host> python3 -m mempalace.mcp_server) swallows pipe errors in the generic exception handler and sleeps forever on the dead channel — while continuing to hold whatever palace locks it acquired. In our production palace (~110k drawers) an orphan held the mine_palace flock idle for ~4 hours, blocking every write until the process was found and killed manually.

The writer-lease self-heal work (#1960, #1971) covers lease recovery after a peer exits — but an orphan that never exits never releases anything. This closes that gap at the source.

Fix

The stdio protocol loop now shuts down cleanly on:

  • stdin EOF — client disconnected;
  • OSError reading stdinEIO/EBADF from orphaned ptys;
  • BrokenPipeError/OSError writing stdout — client side of the pipe gone.

Shutdown returns from the loop for a normal interpreter exit 0 — no os._exit — so held flocks release via ordinary process teardown. A broken stdout is re-pointed at /dev/null first (_drop_broken_stdout), so the interpreter's final flush of the half-written response can't re-raise BrokenPipeError and turn a clean exit into status 120.

Tests

tests/test_mcp_server_eof.py spawns the real server as a subprocess and kills its stdio channel from the outside:

  • closing stdin → process exits 0 promptly;
  • killing the reader side of stdout mid-response → process exits 0, no traceback spam.

Palace isolation via the existing conftest HOME redirect (same mechanism as test_mcp_stdio_protection.py).

Running in production on our fork since 2026-07-12 with no orphan recurrence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NQ7ZKnXm7pnmJ5gVaNhLE4

…ns release locks

A stdio mcp_server orphaned by a dropped SSH session used to swallow
pipe errors in the generic exception handler and sleep forever on the
dead channel, holding the mine_palace flock (2026-07-10 write-path
outage, PID 23392). The loop now logs and shuts down on stdin EOF,
OSError from stdin reads (EIO/EBADF from orphaned ptys), and
BrokenPipeError/OSError on stdout writes. Shutdown returns from the
loop for a normal interpreter exit 0 — no os._exit — so held flocks
release via process teardown; a broken stdout is re-pointed at devnull
first so the shutdown flush cannot turn the exit into status 120.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpEdb9SNWF42BpupHWBhC3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant